/**
 * 
 */
package org.istt.dao.hibernate;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.sql.SQLException;
import java.util.List;

import org.displaytag.properties.SortOrderEnum;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.istt.dao.Dao;
import org.istt.domain.BaseObject;
import org.springframework.orm.ObjectRetrievalFailureException;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;

/**
 * @author Le van Huy
 * 
 */
public abstract class BaseDaoHibernate<T extends BaseObject<ID>, ID extends Serializable> implements
		Dao<T, ID> {
	private Class<T> persistentClass;
	protected HibernateTemplate hibernateTemplate;
    
    @SuppressWarnings("unchecked")
    public BaseDaoHibernate(SessionFactory sessionFactory) {
        this.hibernateTemplate = new HibernateTemplate(sessionFactory);
        
        this.persistentClass = (Class<T>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
    }

	public Class<T> getPersistentClass() {
        return persistentClass;
    }
	
	@Override
	public T findById(ID id, boolean lock) {
		T entity;
        if (lock)
            entity = (T) hibernateTemplate.get(getPersistentClass(), id, LockMode.PESSIMISTIC_WRITE);
        else
            entity = (T) hibernateTemplate.get(getPersistentClass(), id);
        
        if (entity == null) {
            throw new ObjectRetrievalFailureException(getClass(), id);
        }
        return entity;

	}

	@Override
	public List<T> findAll() {
        return findByCriteria();
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public List<T> findByExample(final T exampleInstance, final String... excludeProperty) {
		return hibernateTemplate.execute(new HibernateCallback<List<T>>() {
			
			@Override
			public List<T> doInHibernate(Session session) throws HibernateException,
					SQLException {
				Criteria crit = session.createCriteria(getPersistentClass());
		        Example example =  Example.create(exampleInstance);
		        for (String exclude : excludeProperty) {
		            example.excludeProperty(exclude);
		        }
		        crit.add(example);
		        return crit.list();
			}
		});
	}

	@Override
	public T makePersistent(T entity) {
		if (entity.getId() != null) {
			hibernateTemplate.merge(entity);
		} else {
			hibernateTemplate.saveOrUpdate(entity);
		}
        return entity;
	}

	@Override
	public void makeTransient(T entity) {
		hibernateTemplate.delete(entity);
	}
	
	@Override
	public void removeEntities(List<T> entities){
		hibernateTemplate.deleteAll(entities);
	}
	
	@Override
	public void saveEntities(List<T> entities) {
		hibernateTemplate.saveOrUpdateAll(entities);
	}

	@Override
	public void flush() {
		hibernateTemplate.flush();
	}

	@Override
	public void clear() {
		hibernateTemplate.clear();
	}
	
	 /**
     * Use this inside subclasses as a convenience method.
     */
    @SuppressWarnings("unchecked")
    protected List<T> findByCriteria(final Criterion... criterion) {
    	return hibernateTemplate.execute(new HibernateCallback<List<T>>() {
			
			@Override
			public List<T> doInHibernate(Session session) throws HibernateException,
					SQLException {
				Criteria crit = session.createCriteria(getPersistentClass());
		        for (Criterion c : criterion) {
		            crit.add(c);
		        }
		        return crit.list();
			}
		});
   }
   
   @SuppressWarnings("unchecked")
   @Override
   public List<T> findSimilarTo(final T exampleInstance, final String... excludeProperty) {
	   return hibernateTemplate.execute(new HibernateCallback<List<T>>() {
			
			@Override
			public List<T> doInHibernate(Session session) throws HibernateException,
					SQLException {
				Criteria crit = session.createCriteria(getPersistentClass());
		        Example example =  Example.create(exampleInstance);
		        for (String exclude : excludeProperty) {
		            example.excludeProperty(exclude);
		        }
		        example.excludeZeroes().enableLike(MatchMode.ANYWHERE);
		        crit.add(example);
		        return crit.list();
			}
		});
   };
   
   /*
    * (non-Javadoc)
    * @see org.istt.dao.Dao#getAllRecordsCount(java.lang.Class)
    */
   @SuppressWarnings("unchecked")
   public int getAllRecordsCount(Class<T> clazz) {
       DetachedCriteria criteria = DetachedCriteria.forClass(clazz);
       criteria.setProjection(Projections.rowCount());
       List<Long> results = hibernateTemplate.findByCriteria(criteria);
       int count = results.get(0).intValue();
       return count;
   }
   
   /*
    * (non-Javadoc)
    * @see org.istt.dao.Dao#getAllRecordsPage(java.lang.Class, int, int, org.displaytag.properties.SortOrderEnum, java.lang.String)
    */
   @SuppressWarnings("unchecked")
   public List<T> getAllRecordsPage(Class<T> clazz, int firstResult, int maxResults,
           SortOrderEnum sortDirection, String sortCriterion) {
       DetachedCriteria criteria = DetachedCriteria.forClass(clazz);
       if (sortCriterion != null) {
           if (sortDirection.equals(SortOrderEnum.ASCENDING)) {
               criteria.addOrder(Order.asc(sortCriterion));
           }
           if (sortDirection.equals(SortOrderEnum.DESCENDING)) {
               criteria.addOrder(Order.desc(sortCriterion));
           }
       }
       List<T> results = hibernateTemplate.findByCriteria(criteria,
               firstResult, maxResults);
       return results;
   }
   
   /*
    * (non-Javadoc)
    * @see org.istt.dao.Dao#isUnique(java.lang.String[], java.lang.String[], java.io.Serializable)
    */
   @Override
   public boolean isUnique(final String[] fields, final String[] value, final ID id) {
	   return hibernateTemplate.execute(new HibernateCallback<Boolean>() {
			
			@Override
			public Boolean doInHibernate(Session session) throws HibernateException,
					SQLException {
				Criteria crit = session.createCriteria(getPersistentClass());
				
				if (id != null) {
					crit.add(Restrictions.not(Restrictions.idEq(id)));
				   }
				   
				   for (int i = 0; i < fields.length; i++) {
					   crit.add(Restrictions.ilike(fields[i], value[i], MatchMode.EXACT));
				   }
		        return ((Long)crit.setProjection(Projections.rowCount()).uniqueResult()) == 0l;
			}
		});
   };
	
}
